#include "monad.hpp"

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 需求：在只读环境 `Env`、可变状态 `int` 下写日志 `std::string`
// ========== 定义MyStack及别名 ==========
struct Env
{
    double factor;
};

// MyStack 类型定义
template <class A>
using MyStackInner = StateT<int, A, Identity>;

template <class A>
using MyStackMiddle = ReaderT<Env, A, MyStackInner>;

template <class A>
using MyStack = WriterT<std::string, A, MyStackMiddle>;

// 为MyStack提供便捷操作
namespace MyStackOps
{
    // 提升StateT操作
    template <class A>
    auto liftStateOp(StateT<int, A, Identity> op) -> MyStack<A>
    {
        return liftReader(liftState(op));
    }

    // 提升ReaderT操作
    template <class A>
    auto liftReaderOp(ReaderT<Env, A, Identity> op) -> MyStack<A>
    {
        return liftReader(op);
    }

    // 直接使用WriterT的tell
    auto tell(const std::string& s) -> MyStack<std::monostate> { return WriterT<std::string, std::monostate, MyStackMiddle>::tell(s); }

    // 定义ask（提升Reader::ask）
    auto ask() -> MyStack<Env> { return liftReaderOp(ReaderT<Env, Env, Identity>::ask()); }

    // 定义get（提升State::get）
    auto get() -> MyStack<int> { return liftStateOp(StateT<int, int, Identity>::get()); }

    // 定义put（提升State::put）
    auto put(int s) -> MyStack<std::monostate> { return liftStateOp(StateT<int, std::monostate, Identity>::put(s)); }
}  // namespace MyStackOps

// ========== 使用示例 ==========
auto computation = MyStackOps::tell("start\n") >>= [](auto)
{
    return MyStackOps::ask() >>= [](Env e)
    {
        return MyStackOps::get() >>= [e](int s)
        {
            int n = static_cast<int>(s * e.factor);
            return MyStackOps::tell("factor applied\n") >> MyStackOps::put(n) >> mreturn(n);
        };
    };
};

// 运行复合 Monad
int main()
{
    Env env{2.0};
    int initial_state = 5;
    auto result = runWriter(computation.runWriterT >>=
                            [&](auto&& pair)
                            {
                                auto [a, log] = pair;
                                std::cout << "Result: " << a << "\nLog:\n" << log << std::endl;
                                return mreturn(a);
                            });

    return 0;
}